﻿/***
    Copyright 2012 Stijn Van Antwerpen

    This file is part of Qasparov.

    Qasparov is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Qasparov is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Qasparov. If not, see <http://www.gnu.org/licenses/>.

 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom;
using System.IO;
using System.CodeDom.Compiler;
using Qbus.Communication.Protocol;
using System.Text.RegularExpressions;
using Qbus.Communication;

namespace QBusAPIGenerator
{
    class QBusAPIGenerator
    {
        private string namespaceName;
        private string className;
        private Controller controller;

        private SortedSet<String> usedNames = new SortedSet<String>();

        public QBusAPIGenerator(string namespaceName, string className, Controller controller)
        {
            this.namespaceName = namespaceName;
            this.className = className;
            this.controller = controller;
        }

        public void Generate()
        {
            String myQBusHost = controller.Address;
            String myQBusLogin = controller.Login;
            String myQBusPass = controller.Password;
            List<Module> modules = controller.Modules;

            CSharpCodeProvider cp = new CSharpCodeProvider();
            CodeCompileUnit compileUnit = new CodeCompileUnit();

            var ns = new CodeNamespace(namespaceName);
            compileUnit.Namespaces.Add(ns);

            var imports = new CodeNamespaceImportCollection();
            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
            ns.Imports.Add(new CodeNamespaceImport("System.Linq"));
            ns.Imports.Add(new CodeNamespaceImport("Qbus.Communication"));

            var myQBus = new CodeTypeDeclaration(CleanInput(className));
            myQBus.IsPartial = true;
            ns.Types.Add(myQBus);

            var qbusHostField = new CodeMemberField(new CodeTypeReference(typeof(String)), "qBusHost");
            qbusHostField.InitExpression = new CodeSnippetExpression(quote(myQBusHost));
            qbusHostField.Attributes = MemberAttributes.Const | MemberAttributes.Public | MemberAttributes.Static;
            myQBus.Members.Add(qbusHostField);

            var qbusLogin = new CodeMemberField(new CodeTypeReference(typeof(String)), "qBusLogin");
            qbusLogin.InitExpression = new CodeSnippetExpression(quote(myQBusLogin));
            qbusLogin.Attributes = MemberAttributes.Const | MemberAttributes.Public | MemberAttributes.Static;
            myQBus.Members.Add(qbusLogin);

            var qbusPass = new CodeMemberField(new CodeTypeReference(typeof(String)), "qBusPass");
            qbusPass.InitExpression = new CodeSnippetExpression(quote(""));
            qbusPass.Comments.Add( new CodeCommentStatement("TODO: Set password."));
            qbusPass.Attributes = MemberAttributes.Const | MemberAttributes.Public | MemberAttributes.Static;
            myQBus.Members.Add(qbusPass);

            var controllerField = new CodeMemberField(new CodeTypeReference(controller.GetType()), "controller");
            myQBus.Members.Add(controllerField);

            var ctr = new CodeConstructor();
            ctr.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(controller.GetType()), "controller"));
            ctr.Statements.Add(new CodeSnippetStatement("this.controller = controller;"));
            myQBus.Members.Add(ctr);


            var findModuleMethode = new CodeMemberMethod();
            findModuleMethode.Name = "GetModuleByName";
            findModuleMethode.ReturnType = new CodeTypeReference(new CodeTypeParameter("Qbus.Communication.Protocol.Module"));
            findModuleMethode.Parameters.Add(new CodeParameterDeclarationExpression("String", "moduleName"));
            findModuleMethode.Statements.Add(new CodeSnippetStatement(@"
                //Find the module by it's name
                return controller.Modules.First(m => m.Name.Equals(moduleName));
                "
                ));
            myQBus.Members.Add(findModuleMethode);

            var findPresetMethode = new CodeMemberMethod();
            findPresetMethode.Name = "GetPresetByName";
            findPresetMethode.ReturnType = new CodeTypeReference(new CodeTypeParameter("Qbus.Communication.Protocol.PresetObject"));
            findPresetMethode.Parameters.Add(new CodeParameterDeclarationExpression("String", "presetName"));
            findPresetMethode.Statements.Add(new CodeSnippetStatement(@"
                //Find the module by it's name
                return controller.Presets.First(p => p.Name.Equals(presetName));
                "
                ));
            myQBus.Members.Add(findPresetMethode);


            var onGetModules = new CodeMemberMethod();

            foreach (var m in modules)
            {
                var module = new CodeMemberProperty();
                module.Name = cp.CreateValidIdentifier(GetUniqueName(m.Name));
                module.Attributes = MemberAttributes.Public;
                module.Type = new CodeTypeReference(m.GetType());
                module.GetStatements.Add(new CodeMethodReturnStatement(new CodeCastExpression(module.Type, (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), "GetModuleByName"), new CodePrimitiveExpression(m.Name))))));

                myQBus.Members.Add(module);
            }

            foreach (var p in controller.Presets)
            {
                var preset = new CodeMemberProperty();
                preset.Name = cp.CreateValidIdentifier(GetUniqueName(p.Name));
                preset.Attributes = MemberAttributes.Public;
                preset.Type = new CodeTypeReference(p.GetType());
                preset.GetStatements.Add(new CodeMethodReturnStatement(new CodeCastExpression(preset.Type, (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), "GetPresetByName"), new CodePrimitiveExpression(p.Name))))));

                myQBus.Members.Add(preset);
            }

            // Create a TextWriter to a StreamWriter to the output file. 
            StreamWriter sw = new StreamWriter(className + ".cs", false);

            IndentedTextWriter tw = new IndentedTextWriter(sw, "    ");

            // Generate source code using the code provider.
            cp.GenerateCodeFromCompileUnit(compileUnit, tw,
                new CodeGeneratorOptions());

            // Close the output file.
            tw.Close();

            sw.Close();

        }

        private static string quote(String str)
        {
            return "\"" + str + "\"";
        }

        private string CleanInput(string strIn)
        {
            return Regex.Replace(strIn, @"[^\w]", "");
        }



        private String GetUniqueName(String proposel)
        {
            String result = CleanInput(proposel);
            if (usedNames.Contains(proposel))
            {
                var c = usedNames.Count(n => n.StartsWith(result));
                result = String.Format("{0}_{1}", result, c);
            }
            usedNames.Add(result);
            return result;
        }


    }
}
